﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;

namespace Com.PhilChuang.Utils
{
	/// <summary>
	/// Summary description for MyExtensions
	/// </summary>
	public static partial class Extensions
	{
		#region Object
		public static void ThrowIfNull (this Object self, String objName)
		{
			if (self == null) throw new ArgumentNullException (objName);
		}

		public static TCloneable Clone2<TCloneable> (this TCloneable self) where TCloneable : ICloneable
		{
			if (Object.ReferenceEquals (self, default (TCloneable))) return default (TCloneable);
			return (TCloneable) self.Clone ();
		}

		public static String ToStringOr (this Object self, String nullStr)
		{
			return self == null ? nullStr : self.ToString ();
		}
		#endregion

		#region String
		public static void ThrowIfNullOrEmpty (this String self, String objName)
		{
			if (String.IsNullOrEmpty (self)) throw new ArgumentNullException (objName);
		}

		public static bool IsNullOrEmptyOrBlank (this String self)
		{
			return String.IsNullOrEmpty (self) || String.IsNullOrEmpty (self.Trim ());
		}

		public static String FormatWith (this String self, params Object[] args)
		{
			self.ThrowIfNull ("self");
			return String.Format (self, args);
		}

		public static bool EqualsInsensitive (this String self, String compare)
		{
			if (self == null && compare == null) return true;
			if (self == null || compare == null) return false;
			return self.Trim ().ToUpper ().Equals (compare.Trim ().ToUpper ());
		}

		public static bool StartsWithInsensitive (this String self, String compare)
		{
			self.ThrowIfNull ("self");
			if (String.IsNullOrEmpty (compare)) return true;
			return self.ToUpper ().StartsWith (compare.ToUpper ());
		}

		public static bool ContainsInsensitive (this String self, String compare)
		{
			self.ThrowIfNull ("self");
			if (String.IsNullOrEmpty (compare)) return true;
			return self.ToUpper ().Contains (compare.ToUpper ());
		}

		public static bool ContainsInsensitive (this IEnumerable<String> self, String compare)
		{
			self.ThrowIfNull ("self");
			foreach (String s in self)
			{
				if (s.ContainsInsensitive (compare)) return true;
			}
			return false;
		}

		public static bool IsNumeric (this String self)
		{
			if (self == null) return false;
			// TODO restore in desktop version
			//int temp;
			//return int.TryParse (self, out temp);
			try { int.Parse (self); return true; }
			catch {}
			return false;
		}
		#endregion

		#region Collections
		public static bool In<T> (this T self, IEnumerable<T> list)
		{
			self.ThrowIfNull ("self");
			return list.Contains (self);
		}

		public static bool In<T> (this T self, params T[] list)
		{
			self.ThrowIfNull ("self");
			return self.In (list.ToList ());
		}

		private static readonly Random m_Random = new Random ();

		public static T Random<T> (this IEnumerable<T> self)
		{ return self.Random (m_Random, item => true); }

		public static T Random<T> (this IEnumerable<T> self, Func<T, bool> until)
		{ return self.Random (m_Random, until); }

		public static T Random<T> (this IEnumerable<T> self, Random rnd)
		{ return self.Random (rnd, item => true); }

		public static T Random<T> (this IEnumerable<T> self, Random rnd, Func<T, bool> until)
		{
			self.ThrowIfNull ("self");
			if (self.Count () == 0) throw new IndexOutOfRangeException ();
			T next = default (T);
			do
			{
				next = self.ElementAt (rnd.Next (self.Count ()));
			}
			while (until (next));
			return next;
		}
		#endregion

		#region IEnumerable
		public static bool IsNullOrEmpty<T> (this IEnumerable<T> items)
		{ return items == null || !items.Any (); }

		public static bool CountGreaterThanOrEqualTo<T> (this IEnumerable<T> items, int numItems)
		{
			if (numItems < 0) throw new ArgumentOutOfRangeException ("numItems must be >= 0");

			if (numItems == 0) return true;

			int count = 0;
			foreach (var item in items)
			{
				count++;
				if (count >= numItems) return true;
			}
			return false;
		}

		public static bool CountLessThanOrEqualTo<T> (this IEnumerable<T> items, int numItems)
		{
			if (numItems < 0) throw new ArgumentOutOfRangeException ("numItems must be >= 0");

			if (numItems == 0 && items.IsNullOrEmpty ()) return true;

			int count = 0;
			foreach (var item in items)
			{
				count++;
				if (count > numItems) return false;
			}
			return true;
		}

		public static String Print<T> (this IEnumerable<T> items, String separator)
		{
			if (items.IsNullOrEmpty ()) return String.Empty;

			var sb = new StringBuilder ();
			bool firstTime = true;
			foreach (var i in items)
			{
				if (!firstTime) sb.Append (separator);
				sb.Append (i);
				if (firstTime) firstTime = false;
			}
			return sb.ToString ();
		}
		#endregion

		#region IList
		public static void Replace<T> (this IList<T> list, T toReplace, T replaceWith)
		{
			list.ThrowIfNull ("list");
			int idx = list.IndexOf (toReplace);
			if (idx == -1)
				throw new KeyNotFoundException ();
			list.RemoveAt (idx);
			list.Insert (idx, replaceWith);
		}

		public static void Replace<T> (this IList<T> list, Func<T, bool> selector, T replaceWith)
		{
			list.ThrowIfNull ("list");
			var toReplace = (from i in list where selector (i) select i).FirstOrDefault ();
			if (ReferenceEquals (toReplace, default (T)))
				throw new KeyNotFoundException ();
			int idx = list.IndexOf (toReplace);
			if (idx == -1)
				throw new KeyNotFoundException ();
			list.RemoveAt (idx);
			list.Insert (idx, replaceWith);
		}

		public static bool RemoveWhere<T> (this IList<T> list, Func<T, bool> selector)
		{
			list.ThrowIfNull ("list");
			var toRemove = (from i in list where selector (i) select i).ToList ();
			if (!toRemove.Any ())
				return false;
			foreach (var i in toRemove)
				list.Remove (i);
			return true;
		}
		#endregion

		#region Int
		public static int IntTryParseOrDefault (this String s, int def)
		{
			if (s == null) return def;
			// TODO restore in desktop version
			//int i;
			//if (int.TryParse (s, out i))
			//    return i;
			try { return int.Parse (s); }
			catch {}
			return def;
		}
		#endregion

		#region Double
		/* TODO restore in desktop version
		/// <summary>
		/// Converts from String and checks for invalid number, then rounds to nearest quarter
		/// </summary>
		/// <param name="self"></param>
		/// <returns></returns>
		public static double RoundToNearestQuarter (this double self)
		{
			double num = self;
            num = num * 4;
			num = Math.Round (self, MidpointRounding.AwayFromZero);
            num = num / 4;
            return num;
		}
		*/

		public static double DoubleTryParseOrDefault (this String s, double def)
		{
			if (s == null) return def;
			// TODO restore in desktop version
			//double d;
			//if (double.TryParse (s, out d))
			//    return d;
			try { return double.Parse (s); }
			catch {}
			return def;
		}
		#endregion

		/* TODO restore this in desktop version
		#region SQL
		#region SqlCommand
		public static void BuildInsertQueryFromParams (this SqlCommand self, String tableName, String idParamName)
		{
			self.ThrowIfNull ("self");
			if (String.IsNullOrEmpty (tableName)) throw new ArgumentNullException ("tableName");

			if (!idParamName.StartsWith ("@")) idParamName = "@" + idParamName;

			StringBuilder query = new StringBuilder ();
			query.AppendFormat ("INSERT INTO {0} ", tableName);
			query.AppendFormat ("(");
			bool hasParams = false;
			foreach (SqlParameter parm in self.Parameters)
			{
				if (parm.Direction == ParameterDirection.Input || parm.Direction == ParameterDirection.InputOutput)
				{
					hasParams = true;
					query.AppendFormat ("{0}, ", parm.GetDbColumnName ());
				}
			}
			if (!hasParams) throw new Exception ("No input parameters");
			query.Remove (query.Length - 2, 2);
			query.AppendFormat (")");
			query.AppendFormat (" VALUES ");
			query.AppendFormat ("(");
			foreach (SqlParameter parm in self.Parameters)
			{
				if (parm.Direction == ParameterDirection.Input || parm.Direction == ParameterDirection.InputOutput)
					query.AppendFormat ("{0}, ", parm.GetParameterName ());
			}
			query.Remove (query.Length - 2, 2);
			query.AppendFormat (");");
			if (!String.IsNullOrEmpty (idParamName) 
				&& (self.Parameters[idParamName].Direction == ParameterDirection.Output
					|| self.Parameters[idParamName].Direction == ParameterDirection.InputOutput
					|| self.Parameters[idParamName].Direction == ParameterDirection.ReturnValue))
			{
				query.AppendFormat (" SELECT {0} = SCOPE_IDENTITY();", idParamName);
			}
			self.CommandText = query.ToString ();
			self.Prepare ();
		}

		public static void BuildUpdateQueryFromParams (this SqlCommand self, String tableName, String idParamName)
		{
			self.ThrowIfNull ("self");
			idParamName.ThrowIfNullOrEmpty ("idParamName");
			tableName.ThrowIfNullOrEmpty ("tableName");

			if (!idParamName.StartsWith ("@")) idParamName = "@" + idParamName;

			StringBuilder query = new StringBuilder ();
			query.AppendFormat ("UPDATE {0} ", tableName);
			query.AppendFormat ("SET ");
			bool hasParams = false;
			foreach (SqlParameter parm in self.Parameters)
			{
				if (parm.ParameterName != idParamName
					&& (parm.Direction == ParameterDirection.Input || parm.Direction == ParameterDirection.InputOutput))
				{
					hasParams = true;
					query.AppendFormat ("{0} = {1}, ", parm.GetDbColumnName (), parm.GetParameterName ());
				}
			}
			if (!hasParams) throw new Exception ("No input parameters");
			query.Remove (query.Length - 2, 2);
			query.AppendFormat (" WHERE {0} = {1};", idParamName.Substring (1), idParamName);
			self.CommandText = query.ToString ();
			self.Prepare ();
		}

		private static String GetDbColumnName (this SqlParameter self)
		{
			self.ThrowIfNull ("self");
			if (!self.SourceColumn.IsNullOrEmptyOrBlank ()) return self.SourceColumn;
			if (self.ParameterName.StartsWith ("@")) return self.ParameterName.Substring (1);
			return self.ParameterName;
		}

		private static String GetParameterName (this SqlParameter self)
		{
			self.ThrowIfNull ("self");
			if (!self.ParameterName.StartsWith ("@")) return "@" + self.ParameterName;
			return self.ParameterName;
		}
		#endregion

		public static T Get<T> (this SqlDataReader self, String name)
		{
			Object valObj = self[name];
			if (valObj == DBNull.Value || valObj == null)
				return default (T);
			return (T) valObj;
		}

		public static bool Has (this SqlDataReader self, String name)
		{
			try
			{
				Object valObj = self[name];
				return true;
			}
			catch
			{
				return false;
			}
		}

		public static bool Has (this DataRow self, String name)
		{
			try
			{
				Object valObj = self[name];
				return true;
			}
			catch
			{
				return false;
			}
		}

		public static bool Has (this DataRowView self, String name)
		{
			try
			{
				Object val = self[name];
				return true;
			}
			catch
			{
				return false;
			}
		}

		public static bool TryGet (this DataRowView self, String name, ref Object val)
		{
			try
			{
				val = self[name];
				return true;
			}
			catch
			{
				return false;
			}
		}
		#endregion
		*/

		public static DateTime Min (this DateTime self, DateTime other)
		{
			return self < other ? self : other;
		}

		public static bool EnumTryParse<TEnum> (String enumStr, ref TEnum result)
			where TEnum : struct, IConvertible
		{
			if (!typeof (TEnum).IsEnum)
				throw new ArgumentException ("T must be an enumerated type");

			enumStr.ThrowIfNull ("enumStr");
			
			try
			{
				var result2 = (TEnum) Enum.Parse (typeof (TEnum), enumStr, false);
				result = result2;
				return true;
			}
			catch
			{
				return false;
			}
		}
	}
}